#include <system.h>
#include <pio.h>
#include <asm.h>
#include <std.h>
#include <dint.h>
#include <irq.h>
#include <timer.h>
#include <vbe.h>
#include <error.h>

/*General status values for the vbe*/
static struct vbe_driver	{
	u32 mode;
	u16 curbank;
	u16 bankshift;
}vbedrv;

kdrv vbe;
vbedev vbedevice;

struct vbemodeinfo *ModeInfoBlock;
struct vbeinfo *VbeInfoBlock;
static u8 *phys_base;

/*Get general info about the vbe hardware... Look in vbe.h for the VbeInfoBlock*/
int getVbeInfo()
{
	VbeInfoBlock=(void *)0x700;
	_bioscall(0x4f00,0,0,0,0,0x0700,0x10);
	sane((u16)_eax!=0x4f,"No vesa");
	vbedevice.VbeInfoBlock=VbeInfoBlock;
	return ((u16)_eax==0x4f);
}

int getModeInfo(int mode)
{
    union REGS in,out;
    ModeInfoBlock=(void *)0x800;
    if (mode < 0x100) return 0;     /* Ignore non-VBE modes             */
    in.x.ax=0x4f01;
    in.x.cx=mode;
    in.x.di=0x0800;
   	int86(0x10,&in,&out);
    if (out.x.ax != 0x4F) return 0;
	vbedevice.ModeInfoBlock=ModeInfoBlock;
    return 1;
}

/* Set a VBE video mode */
void setVBEMode(int mode)
{
    union REGS in,out;
    printk("\nSetting mode:0x%x",mode);
	in.x.ax = 0x4F02; in.x.bx = mode;
    int86(0x10,&in,&out);
	sane((out.x.ax)!=0x4f,"No vesa");
}
/* Return the current VBE video mode */
int getVBEMode(void)
{
    union REGS in,out;
    in.x.ax = 0x4F03;
    int86(0x10,&in,&out);
	sane((out.x.ax)!=0x4f,"No vesa");
    return out.x.bx;
}

/*The video buffer generally starts at 0xa0000 (0xa000:0x0000). But you can access only
64 kB of it since the length of a segment is 64 kB. So VESA provides a function which allows
you to write in different locations of the screen with the same video memory..
You just need to change the bank according to your need.

Further vbe has some LFBs thats Linear frame buffers. Which are only accessible in pmode
and are of the range 0xe0000000-0xf0000000... They can be directly accessed without
a bank change..*/

/* Set new read/write bank. Set both Window A and Window B, as many VBE's have
 * these set as separately available read and write windows. We also use a 
 * simple (but very effective) optimization of checking if the requested bank 
 * is currently active. */
void setBank(int bank)
{
    union REGS  in,out;
	sane(bank>48 || bank<0,"Bank!");
    if (bank == vbedrv.curbank) return;    /* Bank is already active           */
    vbedrv.curbank = bank;                 /* Save current bank number         */
    bank <<= vbedrv.bankshift;             /* Adjust to window granularity     */
    in.x.ax = 0x4F05;
    in.x.bx = 0; 
    in.x.dx = bank;
    int86(0x10, &in, &out);
    in.x.ax = 0x4F05;
    in.x.bx = 1; 
    in.x.dx = bank;
    int86(0x10, &in, &out);
}

/*A slow putpixel... For gui thingies you never use putpixel... you use memory routines
that allow fast transfers..*/
void vbe_putpixel(u32 x,u32 y,u32 color)
{
    u32 addr = (u32)y * ModeInfoBlock->BytesPerScanLine + x*(ModeInfoBlock->BitsPerPixel)/8;
    setBank((int)(addr >> 16));
    switch(ModeInfoBlock->BitsPerPixel)	{
    	case 32:
    	*(phys_base + (addr & 0xFFFF)) = (char)(color);
    	*(phys_base + (addr & 0xFFFF)+1) = (char)(color>>8);
    	*(phys_base + (addr & 0xFFFF)+2) = (char)(color>>16);
    	*(phys_base + (addr & 0xFFFF)+3) = (char)(color>>24);
    	break;
    	case 8:
    	*(phys_base + (addr & 0xFFFF)) = (char)(color);
    	break;
    } 
}
/*Gets color of a pixel*/
u32 vbe_getpixel(u32 x,u32 y)
{
	u32 *ph,addr = (u32)y * ModeInfoBlock->BytesPerScanLine + x*(ModeInfoBlock->BitsPerPixel)/8;
	setBank((int)(addr >> 16));
	ph=(u32 *)phys_base;
	return *(ph+addr/((ModeInfoBlock->BitsPerPixel)/8));
}

/*These fns are relatively fast functions for Linear Frame Buffer Modes*/
void putlfb(u32 x,u32 y,u32 color)
{
	u32 *ph,addr=(u32)y * ModeInfoBlock->BytesPerScanLine + x*(ModeInfoBlock->BitsPerPixel)/8;
	ph=(u32 *)ModeInfoBlock->phys_base;
	*(ph+addr/((ModeInfoBlock->BitsPerPixel)/8))=color;
}

u32 getlfb(u32 x,u32 y)
{
	u32 *ph,addr=(u32)y * ModeInfoBlock->BytesPerScanLine + x*(ModeInfoBlock->BitsPerPixel)/8;
	ph=(u32 *)ModeInfoBlock->phys_base;
	return *(ph+addr/((ModeInfoBlock->BitsPerPixel)/8));
}

kdrv vbe;

void list_modes(void)
{
    u32 *p;
    if (!getVbeInfo()) {
		device_error(&vbe,"No VESA VBE");
        return;
    }
    for (p = flat(VbeInfoBlock->VideoModePtr); *p !=(unsigned)-1; p++) {
        if (getModeInfo(*p)) {
            printk("0x%x    %4d x %4d %d bits per pixel\n",(u16)*p,ModeInfoBlock->XResolution, ModeInfoBlock->YResolution,ModeInfoBlock->BitsPerPixel);
        }
    }
}



void availableModes(void)
{
    if (!getVbeInfo())
    {
        device_error(&vbe,"No VESA VBE");
       	return;
    }
    printk("\nVBE v%d.%d(%s)",VbeInfoBlock->VESAVersion >> 8, VbeInfoBlock->VESAVersion & 0xF,flat(VbeInfoBlock->OEMStringPtr));
	//list_modes();
}

/*Initiate a mode that has already been detected to be present in the ModeList given
by VESA*/
u32 init_mode(u32 mode)
{
    int i;
	sane(mode>0x5000,"Mode!");
    if (!getVbeInfo())
    {
        device_error(&vbe,"No VESA VBE detected\n");
        return FAILED;
    }
    i=getModeInfo(mode);
    if(i && (ModeInfoBlock->ModeAttributes & 0x81)==0x81)	{
		phys_base=(u8 *)0xa0000;
		vbedevice.phys_base=phys_base;
    	vbedrv.bankshift = 0;
        while ((unsigned)(64 >> vbedrv.bankshift) != ModeInfoBlock->WinGranularity)	{
        	vbedrv.bankshift++;
    	}
		if(!bit7(ModeInfoBlock->ModeAttributes))	{
			printk("\nCannot start mode.. Not LFB.. No support");
			return FAILED;
		}
    	setVBEMode(mode);
    	return SUCCESS;
    }
    return FAILED;
}

/*Find the color value from the separate rgb values..*/
u32 col(u8 red,u8 green,u8 blue)
{
	return 0xff000000+red*0x10000+green*0x100+blue;
}


u32 find_graphics_mode(u32 x,u32 y,u8 bpp);
int init_vbe()
{
	/*Detect VBE*/
	availableModes(); 
	return NULL;
}

/*Search a mode in the mode list*/
u32 find_graphics_mode(u32 x,u32 y,u8 bpp)
{
	u16 *mode,tmode=0;
	mode=(u16 *)flat(VbeInfoBlock->VideoModePtr);
	while(*mode!=0xffff)	{
		if(getModeInfo(*mode)!=NULL)	{
			if(ModeInfoBlock->XResolution==x && ModeInfoBlock->YResolution==y && ModeInfoBlock->BitsPerPixel==bpp)	{
				if(bit7(ModeInfoBlock->ModeAttributes))
					return (0x4000+*mode);
				else
					stop("\nLFB not supported on this machine..");
			}
		}
		mode++;
	}
	return tmode;
}

int is_lfb()
{
	if(bit7(ModeInfoBlock->ModeAttributes))
		return TRUE;
	return NULL;
}

/*On lfbs you have the privilege of using a memcpy that will give you a give speed
with graphics... Further if you have a pentium you use MMX to make a 64 bit transfer..
In long mode you can go for a 128 bit transfer in SSE...(provided you are in long mode)*/

vbedev vbedevice={
	.getVbeInfo=getVbeInfo,
	.getModeInfo=getModeInfo,
	.getVBEMode=getVBEMode,
	.putpixel=vbe_putpixel,
	.getpixel=vbe_getpixel,
	.putlfb=putlfb,
	.getlfb=getlfb,
	.find_graphics_mode=find_graphics_mode,
	.is_lfb=is_lfb,
	.init_mode=init_mode,
};

kdrv vbe={
	.name="vesabios",
	.init=init_vbe,
	.reset=NULL,
	.deinit=NULL,
	.device_irq=DEVICE_IRQ_UNUSED,
	.device=(void *)&vbedevice,
	.device_desc="\nint (*getVbeInfo)();\
	\nint (*getModeInfo)(int mode);\
	\nint (*getVBEMode)(void);\
	\nvoid (*putpixel)(u32 x,u32 y,u32 color);\
	\nu32 (*getpixel)(u32 x,u32 y);\
	\nvoid (*putlfb)(u32 x,u32 y,u32 color);\
	\nu32 (*getlfb)(u32 x,u32 y);\
	\nu32 (*find_graphics_mode)(u32 x,u32 y,u8 bpp);\
	\nint (*is_lfb)();\
	\nu32 (*init_mode)(u32 mode);\
	\nstruct vbemodeinfo *ModeInfoBlock;\
	\nstruct vbeinfo *VbeInfoBlock;\
	\nu8 *phys_base;",
};

__init(vbe_init,vbe);
